package com.proxyforgame.lifesim;

import java.util.Arrays;

/**
 * Data model of "Life".
 * The field is wrapped in a toroid, so the left edge is closed to the right one and the top edge is closed to the
 * bottom one.
 * Double-buffer is used during the simulation: data come from main buffer mainField, results go to auxiliary buffer
 * backField.
 * When one step of simulation is complete the references to these arrays is swapped.
 * The values stored in the buffers are as following: 1 for alive cell, 0 for dead.
 */
public class LifeModel {

	private byte[]	mainField			= null;
	private byte[]	backField			= null;

	private int		width, height;
	private int[]	neighbourOffsets	= null;
	private int[][]	neighbourXYOffsets	= null;

	/**
	 * Model initialization.
	 * 
	 * @param width field width
	 * @param height field height
	 */
	public LifeModel(int width, int height) {
		this.width = width;
		this.height = height;
		mainField = new byte[width * height];
		backField = new byte[width * height];
		neighbourOffsets = new int[] {-width - 1, -width, -width + 1, -1, 1, width - 1, width, width + 1};
		neighbourXYOffsets = new int[][] { {-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}};
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public void clear() {
		Arrays.fill(mainField, (byte) 0);
	}

	public void setCell(int x, int y, byte c) {
		mainField[y * width + x] = c;
	}

	public byte getCell(int x, int y) {
		return mainField[y * width + x];
	}

	/**
	 * One simulation step.
	 */
	public void simulate() {
		// process inner cells
		for (int y = 1; y < height - 1; y++) {
			for (int x = 1; x < width - 1; x++) {
				int j = y * width + x;
				byte n = countNeighbours(j);
				backField[j] = simulateCell(mainField[j], n);
			}
		}

		// process border cells
		// top and bottom borders
		for (int x = 0; x < width; x++) {
			int j = width * (height - 1);
			byte n = countBorderNeighbours(x, 0);
			backField[x] = simulateCell(mainField[x], n);
			n = countBorderNeighbours(x, height - 1);
			backField[x + j] = simulateCell(mainField[x + j], n);
		}
		// left and right borders
		for (int y = 1; y < height - 1; y++) {
			int j = width * y;
			byte n = countBorderNeighbours(0, y);
			backField[j] = simulateCell(mainField[j], n);
			n = countBorderNeighbours(width - 1, y);
			backField[j + width - 1] = simulateCell(mainField[j + width - 1], n);
		}

		// swap buffers
		byte[] t = mainField;
		mainField = backField;
		backField = t;
	}

	/**
	 * Counts neighbours of the specified cell not touching the field borders.
	 * 
	 * @param j offset of the cell in a buffer
	 * @return number of neighbour cells
	 */
	private byte countNeighbours(int j) {
		byte n = 0;
		for (int i = 0; i < 8; i++) {
			n += mainField[j + neighbourOffsets[i]];
		}
		return n;
	}

	/**
	 * Counts neighbours of the specified cell touching the field borders.
	 * 
	 * @param x x-coord of the cell
	 * @param y y-coord of the cell
	 * @return number of neighbour cells
	 */
	private byte countBorderNeighbours(int x, int y) {
		byte n = 0;
		for (int i = 0; i < 8; i++) {
			int bx = (x + neighbourXYOffsets[i][0] + width) % width;
			int by = (y + neighbourXYOffsets[i][1] + height) % height;
			n += mainField[by * width + bx];
		}
		return n;
	}

	/**
	 * Simulation step for one cell.
	 * 
	 * @param self own value of the cell: 0/1
	 * @param neighbors number of neighbour cells
	 * @return new new value of the cell: 0/1
	 */
	private byte simulateCell(byte self, byte neighbors) {
		return (byte) (self == 0 ? (neighbors == 3 ? 1 : 0) : neighbors == 2 || neighbors == 3 ? 1 : 0);
	}

}
